from typing import Set

from chernc.treediff.gumtree.matchers.mapping_store import MappingStore
from chernc.treediff.gumtree.tree.tree import TreeLike

class SimilarityMetrics:

    @staticmethod
    def chawathe_similarity(src: TreeLike, dst: TreeLike, mappings: MappingStore[TreeLike]) -> float:
        max_size = max(len(src.get_descendants()), len(dst.get_descendants()))
        return float(SimilarityMetrics.number_of_mapped_descendants(src, dst, mappings)) / float(max_size)

    @staticmethod
    def overlap_similarity(src: TreeLike, dst: TreeLike, mappings: MappingStore[TreeLike]) -> float:
        min_size = min(len(src.get_descendants()), len(dst.get_descendants()))
        return float(SimilarityMetrics.number_of_mapped_descendants(src, dst, mappings)) / float(min_size)

    @staticmethod
    def dice_similarity(src: TreeLike, dst: TreeLike, mappings: MappingStore[TreeLike]) -> float:
        common = SimilarityMetrics.number_of_mapped_descendants(src, dst, mappings)
        return SimilarityMetrics.dice_coefficient(common, len(src.get_descendants()), len(dst.get_descendants()))

    @staticmethod
    def jaccard_similarity(src: TreeLike, dst: TreeLike, mappings: MappingStore[TreeLike]) -> float:
        common = SimilarityMetrics.number_of_mapped_descendants(src, dst, mappings)
        return SimilarityMetrics.jaccard_index(common, len(src.get_descendants()), len(dst.get_descendants()))

    @staticmethod
    def dice_coefficient(common_elements_nb: int, left_elements_nb: int, right_elements_nb: int) -> float:
        return 2.0 * common_elements_nb / (left_elements_nb + right_elements_nb)

    @staticmethod
    def jaccard_index(common_elements_nb: int, left_elements_nb: int, right_elements_nb: int) -> float:
        denominator = (left_elements_nb + right_elements_nb - common_elements_nb)
        return common_elements_nb / denominator if denominator != 0 else 0.0

    @staticmethod
    def number_of_mapped_descendants(src: TreeLike, dst: TreeLike, mappings: MappingStore[TreeLike]) -> int:
        dst_descendants: Set[TreeLike] = set(dst.get_descendants())
        mapped_descendants = 0

        for src_descendant in src.get_descendants():
            if mappings.is_src_mapped(src_descendant):
                dst_for_src_descendant = mappings.get_dst_for_src(src_descendant)
                if dst_for_src_descendant in dst_descendants:
                    mapped_descendants += 1

        return mapped_descendants
